package com.enzi.webclient.proxys;

import com.enzi.webclient.ApiServer;
import com.enzi.webclient.beans.MethodInfo;
import com.enzi.webclient.beans.ServerInfo;
import com.enzi.webclient.interfaces.ProxyCreator;
import com.enzi.webclient.interfaces.RestHandler;
import com.enzi.webclient.restHandlers.WebClientRestHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 使用JDK动态代理实现代理类
 *
 * @Author: enzi.cheng
 * @Date: 2019/1/15 09:01
 * @Description:
 */
@Slf4j
public class JDKProxyCreator implements ProxyCreator {
   @Override
    public Object createProxy(Class<?> type) {
        log.info("createProxy:" + type);
        //根据接口得到API服务器信息
        ServerInfo serverInfo = extractServerInfo(type);
        log.info("serverInfo:" + serverInfo);
        //给每一个代理类一个实现
        RestHandler handler = new WebClientRestHandler();

        //初始化服务器信息
        handler.init(serverInfo);
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() {
            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

                //根据方法和参数得到调用信息
                MethodInfo methodInfo = extractMethodInfo(method, objects);
                log.info("methodInfo:" + methodInfo);

                return handler.invokeRest(methodInfo);
            }

            /**
             * 根据方法定义和调用参数得到调用的相关信息
             * @param method
             * @param objects
             * @return
             */
            private MethodInfo extractMethodInfo(Method method, Object[] objects) {

                MethodInfo methodInfo = new MethodInfo();

                extractUrlAndMethod(method, methodInfo);

                extractParamAndBody(method, objects, methodInfo);

                extractReturnInfo(method, methodInfo);
                return methodInfo;

            }

            /**
             * 提取返回对象信息
             * @param method
             * @param methodInfo
             */
            private void extractReturnInfo(Method method, MethodInfo methodInfo) {
                //返回flux还是Mono

                boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class);
                methodInfo.setReturnFlux(isFlux);

                //得到泛型类型的实际类型
                Class<?> elementType = extractElementType(method.getGenericReturnType());
                methodInfo.setReturnElementType(elementType);
            }


        });
    }

    /**
     * 得到泛型类型的实际类型
     * @param genericReturnType
     * @return
     */
    private Class<?> extractElementType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        return (Class<?>) actualTypeArguments[0];
    }
    /**
     * 得到调用的参数和body
     * @param method
     * @param objects
     * @param methodInfo
     */
    private void extractParamAndBody(Method method, Object[] objects, MethodInfo methodInfo) {
        //得到调用的参数和body
        Parameter[] parameters = method.getParameters();

        //参数和值对应的map
        Map<String, Object> params = new LinkedHashMap<>(4);
        methodInfo.setParams(params);

        for (int i = 0; i < parameters.length; i++) {

            //是否带@PathVariable
            PathVariable annoPath = parameters[i].getAnnotation(PathVariable.class);

            if (null != annoPath) {
                params.put(annoPath.value(), objects[i]);
            }
            RequestParam annoRequest = parameters[i].getAnnotation(RequestParam.class);
            if (null != annoRequest) {
                params.put(annoRequest.value(), objects[i]);
            }
            //是否带 RequestBody
            RequestBody annoBody = parameters[i].getAnnotation(RequestBody.class);

            if (null != annoBody) {
//                if(objects[i] instanceof  Mono){
                    methodInfo.setBody((Mono<?>) objects[i]);
//                }else{
//                    methodInfo.setBody(objects[i]);
//                }
                methodInfo.setReturnElementType(extractElementType(parameters[i].getParameterizedType()));

            }
        }
    }

    /**
     * 得到请求URL和请求方法
     * @param method
     * @param methodInfo
     */
    private void extractUrlAndMethod(Method method, MethodInfo methodInfo) {
        //得到请求URL和请求方法
        Annotation[] annotations = method.getAnnotations();

        for (Annotation annotation : annotations) {
            //GET
            if (annotation instanceof GetMapping) {
                GetMapping a = (GetMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.GET);
            }
            //POST
            else if (annotation instanceof PostMapping) {
                PostMapping a = (PostMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.POST);
            }
            //PUT
            else if (annotation instanceof PutMapping) {
                PutMapping a = (PutMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.PUT);
            }
            //DELETE
            else if (annotation instanceof DeleteMapping) {
                DeleteMapping a = (DeleteMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.DELETE);
            }
        }
    }

    /**
     * 提取服务器信息
     *
     * @param type
     * @return
     */
    private ServerInfo extractServerInfo(Class<?> type) {
        ServerInfo serverInfo = new ServerInfo();

        ApiServer anno = type.getAnnotation(ApiServer.class);
        serverInfo.setUrl(anno.value());
        return serverInfo;
    }


}
